using System;

namespace Metar.NET
{
	public class MetarInformation
    {

        #region public variables

        public const int CLOUDS_MAX_GROUP=3;
        public const int VISIBILITY_MAX_GROUP=3;
        public const int WEATHER_MAX_GROUP=3;
        public const int RUNWAY_MAX_GROUP=4;
        public int clouds_group_number=0;
        public int visibility_group_number=0;
        public int weather_group_number=0;
        public int runway_group_number=0;
        public string icao="";
        public string type="";
        public string str_metar="";
        public string remarks="";
        public string report_mod="";
        public Wind wind=null;
        public Visibility[] visibility_array=null;
        public Weather[] weather_array=null;
        public Clouds[] clouds_array=null;
        public Temp temperature=null;
        public Pressure pressure=null;
        public Humidity humidity=null;
        public Runway[] runway_array=null;
        public Precipitation precipitation=null;
        public System.DateTime report_time;

        #endregion

        /// <summary>
        /// decodes specified metar string.
        /// </summary>
        /// <param name="str_metar">Metar string.</param>
        public void decodeMetar(string str_metar)
        {
            this.clouds_group_number=0;
            this.visibility_group_number=0;
            this.weather_group_number=0;
            this.runway_group_number=0;

            this.wind=new Wind();
            this.runway_array=new Runway[RUNWAY_MAX_GROUP];
            for (int cnt=0;cnt<RUNWAY_MAX_GROUP;cnt++)
            {
                this.runway_array[cnt]=new Runway();
            }
            this.visibility_array=new Visibility[VISIBILITY_MAX_GROUP];
            for (int cnt=0;cnt<VISIBILITY_MAX_GROUP;cnt++)
            {
                this.visibility_array[cnt]=new Visibility();
            }
            this.weather_array=new Weather[WEATHER_MAX_GROUP];
            for (int cnt=0;cnt<WEATHER_MAX_GROUP;cnt++)
            {
                this.weather_array[cnt]=new Weather();
            }
            this.clouds_array=new Clouds[CLOUDS_MAX_GROUP];
            for (int cnt=0;cnt<CLOUDS_MAX_GROUP;cnt++)
            {
                this.clouds_array[cnt]=new Clouds();
            }
            this.temperature=new Temp();
            this.pressure=new Pressure();
            this.humidity=new Humidity();
            this.precipitation=new Precipitation();

            /* initialization */
            string temp_visibility_miles = "";
            this.remarks = "";

            this.str_metar=str_metar;
            string[] parts = this.str_metar.Split(' ');
            int num_parts = parts.Length;
            int month=1;
            int year=System.DateTime.UtcNow.Year;
            string[] regs=null;
            string part;
            for (int i = 0; i < num_parts; i++)
            {
                part = parts[i];
                if (Ereg.ereg("RMK|TEMPO|BECMG|INTER",part))
                {
                    /* The rest of the METAR is either a remark or temporary
                    * information. We skip the rest of the METAR. 
                    */
                    this.remarks += " " + part;
                    break;
                }
                else if (part == "METAR")
                {
                    /*
                    * Type of Report: METAR
                    */
                    this.type = "METAR";
                }
                else if (part == "SPECI")
                {
                    /*
                    * Type of Report: SPECI
                    */
                    this.type = "SPECI";
                }
                else if (Ereg.ereg("^[A-Z]{4}$",part) && (this.icao==""))
                {
                    /*
                    * Station Identifier
                    */
                    this.icao = part;
                }
                else if (Ereg.ereg("([0-9]{2})([0-9]{2})([0-9]{2})Z",part,ref regs))
                {
                    /*
                    * Date and Time of Report.
                    *
                    * We return a standard Unix UTC/GMT timestamp suitable for
                    * gmdate().
                    */
                    // date is like ddhhmmZ
                    if (System.Convert.ToInt32(regs[1]) > System.DateTime.UtcNow.Day)
                    {
                        /* The day is greather that the current day of month => the
                        * report is from last month. 
                        */
                        month = System.DateTime.UtcNow.Month - 1;
                        if (month<1)
                        {
                            month=12;
                            year=System.DateTime.UtcNow.Year-1;
                        }
                    }
                    else
                    {
                        month = System.DateTime.UtcNow.Month;
                    }
                    this.report_time =new System.DateTime(year,
                                                            month,
                                                            System.Convert.ToInt32(regs[1]),// day
                                                            System.Convert.ToInt32(regs[2]),// hour
                                                            System.Convert.ToInt32(regs[3]),// min
                                                            0// sec
                                                            );
                }
                else if (Ereg.ereg("(AUTO|COR|RTD|CC[A-Z]|RR[A-Z])", part,ref regs))
                {
                    /*
                    * Report Modifier: AUTO, COR, CCx or RRx
                    */
                    this.report_mod = regs[1];
                }
                else if (Ereg.ereg("([0-9]{3}|VRB)([0-9]{2,3})G?([0-9]{2,3})?(KT|MPS|KMH)", part,ref regs))
                {
                    /* Wind Group */
                    this.wind.b_no_data=false;

                    if (regs[1]=="VRB")
                        this.wind.direction.deg=WindDir.VRB;
                    else
                        this.wind.direction.deg = System.Convert.ToDouble(regs[1]);

                    this.wind.speed.set_speed(System.Convert.ToDouble(regs[2]),regs[4]);

                    if (regs[3]!="")
                    {
                        /* We have a report with information about the gust.
                        * First we have the gust measured in knots.
                        */
                        this.wind.gust_speed.set_speed(System.Convert.ToDouble(regs[3]),regs[4]);
                    }
                }
                else if (Ereg.ereg("^([0-9]{3})V([0-9]{3})$", part,ref regs) && (this.wind!=null))
                {
                    /*
                    * Variable wind-direction
                    */
                    this.wind.direction.var_beg = System.Convert.ToDouble(regs[1]);
                    this.wind.direction.var_end = System.Convert.ToDouble(regs[2]);
                }
                else if (Ereg.ereg("^([0-9]{4})([NS]?[EW]?)$", part,ref regs))
                {
                    if (visibility_group_number<VISIBILITY_MAX_GROUP)
                    {
                        this.visibility_array[visibility_group_number].b_no_data=false;
                        /* 
                        * Visibility in meters (4 digits only)
                        */
                        if (regs[1] == "0000")
                        {
                            /* Special low value */
                            this.visibility_array[visibility_group_number].prefix = -1; /* Less than */
                            this.visibility_array[visibility_group_number].length.Meter  = 50;
                        }
                        else if (regs[1] == "9999")
                        {
                            /* Special high value */
                            this.visibility_array[visibility_group_number].prefix = 1; 
                            this.visibility_array[visibility_group_number].length.Meter  = 10000;
                        }
                        else
                        {
                            /* Normal visibility, returned in both small and large units. */
                            this.visibility_array[visibility_group_number].prefix = 0; 
                            this.visibility_array[visibility_group_number].length.Meter  = System.Convert.ToDouble(regs[1]);
                        }
                        if (regs[2]!="")
                        {
                            this.visibility_array[visibility_group_number].dir = regs[2];
                        }
                        visibility_group_number++;
                    }
                }
                else if (Ereg.ereg("^[0-9]$", part))
                {
                    /*
                    * Temp Visibility Group, single digit followed by space.
                    */
                    temp_visibility_miles = part;
                }
                else if (Ereg.ereg("^M?(([0-9]?)[ ]?([0-9])(/?)([0-9]*))SM$",
                    temp_visibility_miles +" "+part,ref regs))
                {
                    if (visibility_group_number<VISIBILITY_MAX_GROUP)
                    {
                        this.visibility_array[visibility_group_number].b_no_data=false;
                        /*
                        * Visibility Group
                        */
                        double vis_miles=0;
                        if (regs[4] == "/")
                        {
                            if (regs[2]!="")
                                vis_miles = System.Convert.ToDouble(regs[2]);
                            if ((regs[3]!="")&&(regs[5]!=""))
                                vis_miles += System.Convert.ToDouble(regs[3])/System.Convert.ToDouble(regs[5]);
                        }
                        else
                        {
                            if (regs[1]!="")
                                vis_miles = System.Convert.ToDouble(regs[1]);
                        }
                        if (regs[0][0] == 'M')
                        {
                            /* Prefix - less than */
                            this.visibility_array[visibility_group_number].prefix = -1;
                        }
                        else
                        {
                            this.visibility_array[visibility_group_number].prefix = 0;
                        }
                        /* The visibility measured in miles */
                        this.visibility_array[visibility_group_number].length.Miles = vis_miles;
                        visibility_group_number++;
                    }
                }
                else if (part == "CAVOK")
                {
                    if (visibility_group_number<VISIBILITY_MAX_GROUP)
                    {
                        this.visibility_array[visibility_group_number].b_no_data=false;
                        /* CAVOK is used when the visibility is greater than 10
                        * kilometers, the lowest cloud-base is at 5000 feet or more
                        * and there is no significant weather.
                        */
                        this.visibility_array[visibility_group_number].prefix = 1; 
                        this.visibility_array[visibility_group_number].length.Meter  = 10000;

                        this.clouds_array[0].Condition = "CAVOK";
                        visibility_group_number++;
                    }
                }
                else if (Ereg.ereg("^R([0-9]{2})([RLC]?)/([MP]?)([0-9]{4})" +
                    "([DNU]?)V?(P?)([0-9]{4})?([DNU]?)$", part,ref regs))
                {
                    /* Runway-group */
                    if (runway_group_number<RUNWAY_MAX_GROUP)
                    {
                        this.runway_array[runway_group_number].b_no_data=false;
                    
                        this.runway_array[runway_group_number].nr = regs[1];
                        if (regs[2]!="")
                        {
                            this.runway_array[runway_group_number].approach = regs[2];
                        }
                        if (regs[7]!="")
                        {
                            /* We have both min and max visibility since regs[7] holds
                            * the max visibility.
                            */
                            if (regs[5]!="")
                            { 
                                /* regs[5] is tendency for min visibility. */
                                this.runway_array[runway_group_number].min_tendency = regs[5];
                            }
                            if (regs[8]!="")
                            { 
                                /* regs[8] is tendency for max visibility. */
                                this.runway_array[runway_group_number].max_tendency = regs[8];
                            }
                            if (regs[3] == "M")
                            {
                                /* Less than. */
                                this.runway_array[runway_group_number].min_prefix = -1;
                            }
                            this.runway_array[runway_group_number].min_length.Meter = System.Convert.ToDouble(regs[4]);
                            if (regs[6] == "P")
                            {
                                /* Greater than. */
                                this.runway_array[runway_group_number].max_prefix = 1;
                            }
                            this.runway_array[runway_group_number].max_length.Meter = System.Convert.ToDouble(regs[7]);
                        }
                        else
                        {
                            /* We only have a single visibility. */
                            if (regs[5]!="")
                            { 
                                /* regs[5] holds the tendency for visibility. */
                                this.runway_array[runway_group_number].tendency = regs[5];
                            }
                            if (regs[3] == "M")
                            {
                                /* Less than. */
                                this.runway_array[runway_group_number].prefix = -1;
                            }
                            else if (regs[3] == "P")
                            {
                                /* Greater than. */
                                this.runway_array[runway_group_number].prefix = 1;
                            }
                            this.runway_array[runway_group_number].length.Meter = System.Convert.ToDouble(regs[4]);
                        }
                        runway_group_number++;
                    }
                }
                else if (Ereg.ereg("^(VC)?" +       // Proximity
                    "(-|\\+)?" +                         // Intensity
                    "(MI|PR|BC|DR|BL|SH|TS|FZ)?" +       // Descriptor
                    "((DZ|RA|SN|SG|IC|PL|GR|GS|UP)+)?" + // Precipitation
                    "(BR|FG|FU|VA|DU|SA|HZ|PY)?" +       // Obscuration
                    "(PO|SQ|FC|SS)?$",                   // Other
                    part,ref regs)
                    )
                {

                    // Current weather-group.
                    if (weather_group_number<WEATHER_MAX_GROUP)
                    {
                        this.weather_array[weather_group_number].b_no_data=false;

                        this.weather_array[weather_group_number].proximity     = regs[1];
                        this.weather_array[weather_group_number].intensity     = regs[2];
                        this.weather_array[weather_group_number].descriptor    = regs[3];
                        this.weather_array[weather_group_number].precipitation = regs[4];
                        this.weather_array[weather_group_number].obscuration   = regs[6];
                        this.weather_array[weather_group_number].other         = regs[7];
                        weather_group_number++;
                    }
                }

                else if (part == "SKC" || part == "CLR")
                {
                    // Cloud-group
                    if (clouds_group_number<CLOUDS_MAX_GROUP)
                    {
                        this.clouds_array[clouds_group_number].b_no_data=false;
                        this.clouds_array[clouds_group_number].Condition = part;
                        this.clouds_group_number++;
                    }
                }
                else if (Ereg.ereg("^(VV|FEW|SCT|BKN|OVC)([0-9]{3}|///)" +
                    "(CB|TCU)?$", part,ref regs))
                {
                    if (clouds_group_number<CLOUDS_MAX_GROUP)
                    {
                        this.clouds_array[clouds_group_number].b_no_data=false;
                        /* We have found (another) a cloud-layer-group. */
                        this.clouds_array[clouds_group_number].Condition = regs[1];
                        if (regs[3]!="")
                        {
                            this.clouds_array[clouds_group_number].cumulus = regs[3];
                        }
                        if (regs[2] == "000")
                        {
                            /* '000' is a special height. */
                            this.clouds_array[clouds_group_number].length.Ft     = 100;
                            this.clouds_array[clouds_group_number].prefix = -1; /* Less than */
                        }
                        else if (regs[2] == "///")
                        {
                            /* '///' means height nil */
                            this.clouds_array[clouds_group_number].length.Ft     = Clouds.NIL;
                        }
                        else
                        {
                            this.clouds_array[clouds_group_number].length.Ft = System.Convert.ToDouble(regs[2]) *100;
                        }
                        this.clouds_group_number++;
                    }
                }

                else if (Ereg.ereg("^(M?[0-9]{2})/(M?[0-9]{2})?$", part,ref regs))
                {
                    /*
                    * Temperature/Dew Point Group.
                    */
                    this.temperature.b_no_data=false;
                    this.temperature.current.Celcius =System.Convert.ToDouble(regs[1].Replace("M", "-"));
                    if (regs[2]!="")
                    {
                        this.temperature.dew.Celcius =System.Convert.ToDouble(regs[2].Replace("M", "-"));
                    }
                }
                else if (Ereg.ereg("A([0-9]{4})", part,ref regs))
                {
                    /*
                    * Altimeter.
                    * The pressure measured in inHg.
                    */
                    this.pressure.b_no_data=false;
                    this.pressure.Inhg = System.Convert.ToDouble(regs[1])/100;
                }
                else if (Ereg.ereg("Q([0-9]{4})", part,ref regs))
                {
                    /*
                    * Altimeter.
                    * The specification doesn't say anything about
                    * the Qxxxx-form, but it's in the METARs.
                    */
                    this.pressure.b_no_data=false;
                    /* The pressure measured in hPa */
                    this.pressure.Hpa  = System.Convert.ToDouble(regs[1]);
                }
                else if (Ereg.ereg("^T([0-9]{4})([0-9]{4})", part,ref regs))
                {
                    /*
                    * Temperature/Dew Point Group, coded to tenth of degree Celsius.
                    */
                    this.temperature.current.Tenth=((int)(System.Convert.ToDouble(regs[1])/10)).ToString();
                    this.temperature.dew.Tenth=((int)(System.Convert.ToDouble(regs[2])/10)).ToString();
                }
                else if (Ereg.ereg("^T([0-9]{4}$)", part,ref regs))
                {
                    this.temperature.current.Tenth=((int)(System.Convert.ToDouble(regs[1])/10)).ToString();
                }
                else if (Ereg.ereg("^1([0-9]{4}$)", part,ref regs))
                {
                    /*
                    * 6 hour maximum temperature Celsius, coded to tenth of degree
                    */
                    this.temperature.temp_min_max.max6h.Tenth=((int)(System.Convert.ToDouble(regs[1])/10)).ToString();
                }
                else if (Ereg.ereg("^2([0-9]{4}$)", part,ref regs))
                {
                    /*
                    * 6 hour minimum temperature Celsius, coded to tenth of degree
                    */
                    this.temperature.temp_min_max.min6h.Tenth=((int)(System.Convert.ToDouble(regs[1])/10)).ToString();
                }
                else if (Ereg.ereg("^4([0-9]{4})([0-9]{4})$", part,ref regs))
                {
                    /*
                    * 24 hour maximum and minimum temperature Celsius, coded to
                    * tenth of degree
                    */
                    this.temperature.temp_min_max.max24h.Tenth=((int)(System.Convert.ToDouble(regs[1])/10)).ToString();
                    this.temperature.temp_min_max.min24h.Tenth=((int)(System.Convert.ToDouble(regs[2])/10)).ToString();
                }
                else if (Ereg.ereg("^P([0-9]{4})", part,ref regs))
                {
                    /*
                    * Precipitation during last hour in hundredths of an inch
                    */
                    this.precipitation.b_no_data=false;

                    if (regs[1] == "0000")
                    {
                        this.precipitation.height.Inch = -1;
                    }
                    else
                    {
                        this.precipitation.height.Inch =System.Convert.ToDouble(regs[1])/100;
                    }
                }
                else if (Ereg.ereg("^6([0-9]{4})", part,ref regs))
                {
                    /*
                    * Precipitation during last 3 or 6 hours in hundredths of an
                    * inch.
                    */
                    if (regs[1] == "0000")
                    {
                        this.precipitation.height_6h.Inch = -1;
                    }
                    else
                    {
                        this.precipitation.height_6h.Inch =System.Convert.ToDouble(regs[1])/100;
                    }
                }
                else if (Ereg.ereg("^7([0-9]{4})", part,ref regs))
                {
                    /*
                    * Precipitation during last 24 hours in hundredths of an inch.
                    */
                    if (regs[1] == "0000")
                    {
                        this.precipitation.height_24h.Inch = -1;
                    }
                    else
                    {
                        this.precipitation.height_24h.Inch =System.Convert.ToDouble(regs[1])/100;
                    }
                }
                else if (Ereg.ereg("^4/([0-9]{3})", part,ref regs))
                {
                    /*
                    * Snow depth in inches
                    */
                    if (regs[1] == "0000")
                    {
                        this.precipitation.height_snow.Inch = -1;
                    }
                    else
                    {
                        this.precipitation.height_snow.Inch = System.Convert.ToDouble(regs[1]);
                    }
                }
                else
                {
                    /*
                    * If we couldn't match the group, we assume that it was a
                    * remark.
                    */
                    this.remarks+= " " + part;
                }
            }
            /*
            * Relative humidity
            */
            if ((this.temperature.current.Celcius!=0) && (this.temperature.dew.Celcius!=0))
            {
                this.humidity.b_no_data=false;
                this.humidity.relative =System.Math.Pow(10, (1779.75 * (this.temperature.dew.Celcius -
                    this.temperature.current.Celcius)
                    / ((237.3 + this.temperature.dew.Celcius) *
                    (237.3 + this.temperature.current.Celcius))
                    + 2));
            } 
            /*
            *  Compute windchill if temp < 40f and windspeed > 3 mph
            */
            if ( (this.temperature.current.Fahrenheit!=0) && 
                this.temperature.current.Fahrenheit <= 40 &&
                (this.wind.speed.Mileshours!=0) &&
                this.wind.speed.Mileshours > 3
                )
            {
                this.temperature.windchill.Fahrenheit = 35.74 + 0.6215*this.temperature.current.Fahrenheit 
                    - 35.75*System.Math.Pow(this.wind.speed.Mileshours, 0.16) 
                    + 0.4275*this.temperature.current.Fahrenheit * 
                    System.Math.Pow(this.wind.speed.Mileshours, 0.16);
            }
            /*
            * Compute heat index if temp > 70F
            */
            if ( (this.temperature.current.Fahrenheit!=0) &&
                this.temperature.current.Fahrenheit > 70 &&
                (this.humidity.relative!=0)
                )
            {
                this.temperature.heatindex.Fahrenheit =-42.379
                    + 2.04901523 * this.temperature.current.Fahrenheit
                    + 10.1433312 * this.humidity.relative
                    - 0.22475541 * this.temperature.current.Fahrenheit
                    * this.humidity.relative
                    - 0.00683783 * this.temperature.current.Fahrenheit
                    * this.temperature.current.Fahrenheit
                    - 0.05481717 * this.humidity.relative
                    * this.humidity.relative
                    + 0.00122874 * this.temperature.current.Fahrenheit
                    * this.temperature.current.Fahrenheit
                    * this.humidity.relative
                    + 0.00085282 * this.temperature.current.Fahrenheit
                    * this.humidity.relative
                    * this.humidity.relative
                    - 0.00000199 * this.temperature.current.Fahrenheit
                    * this.temperature.current.Fahrenheit
                    * this.humidity.relative
                    * this.humidity.relative;
            }
            /*
            * Compute the humidity index
            */
            if (this.humidity.relative!=0)
            {
                double e = (6.112 * System.Math.Pow(10, 7.5 * this.temperature.current.Celcius
                    / (237.7 + this.temperature.current.Celcius))
                    * this.humidity.relative / 100) - 10;
                this.humidity.index.Celcius =this.temperature.current.Celcius + 5.0/9 * e;
            }
        }

	}
}
